Basic Tutorial 6
From Ogre Wiki
Beginner Tutorial 6: The Ogre Startup Sequence
Any problems you encounter while working with this tutorial should be posted to the Help Forum.
Contents |
Prerequisites
This tutorial assumes you have knowledge of C++ programming and are able to setup and compile an Ogre application (if you have trouble setting up your application, see this guide for specific compiler setups). This tutorial builds on the previous beginner tutorials, and it assumes you have already worked through them.
Introduction
In this tutorial you will learn how to start up Ogre without using the example framework. By the time you are done working through this, you should be able to create your own Ogre applications which do not use the ExampleApplication or the ExampleFrameListener.
You can find the code for this tutorial here. As you go through the tutorial you should be slowly adding code to your own project and watching the results as we build it.
Getting Started
The Initial Code
In this tutorial, we will be using a predefined code base as a starting point. As long as you have worked through the previous tutorials, this should all be familiar to you. Create a project in the compiler of your choice for this project, and add a source file which contains this code:
#include <Ogre.h>
#include <OIS/OIS.h>
#include <CEGUI/CEGUI.h>
#include <CEGUI/RendererModules/Ogre/CEGUIOgreRenderer.h>
using namespace Ogre;
class ExitListener : public FrameListener
{
public:
ExitListener(OIS::Keyboard *keyboard)
: mKeyboard(keyboard)
{
}
bool frameStarted(const FrameEvent& evt)
{
mKeyboard->capture();
return !mKeyboard->isKeyDown(OIS::KC_ESCAPE);
}
private:
OIS::Keyboard *mKeyboard;
};
class Application
{
public:
void go()
{
createRoot();
defineResources();
setupRenderSystem();
createRenderWindow();
initializeResourceGroups();
setupScene();
setupInputSystem();
setupCEGUI();
createFrameListener();
startRenderLoop();
}
~Application()
{
}
private:
Root *mRoot;
OIS::Keyboard *mKeyboard;
OIS::InputManager *mInputManager;
CEGUI::OgreRenderer *mRenderer;
ExitListener *mListener;
void createRoot()
{
}
void defineResources()
{
}
void setupRenderSystem()
{
}
void createRenderWindow()
{
}
void initializeResourceGroups()
{
}
void setupScene()
{
}
void setupInputSystem()
{
}
void setupCEGUI()
{
}
void createFrameListener()
{
}
void startRenderLoop()
{
}
};
#if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT)
#else
int main(int argc, char **argv)
#endif
{
try
{
Application app;
app.go();
}
catch(Exception& e)
{
#if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32
MessageBoxA(NULL, e.getFullDescription().c_str(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
fprintf(stderr, "An exception has occurred: %s\n",
e.getFullDescription().c_str());
#endif
}
return 0;
}
Be sure you can compile this code before continuing. If you are having errors with missing CEGUI dependencies such as CEGUISingleton.h or linker problems, make sure the following paths are in the include search paths directive: $(OGRE_HOME)\include, $(OGRE_HOME)\include\CEGUI, and $(OGRE_HOME)\samples\include. You will also need to add CEGUIBase_d.lib and OgreGUIRenderer_d.lib to the linker dependencies.
The Startup Process in a Nutshell
Once you understand what's going on under the hood, getting Ogre running is actually very easy to do. The example framework looks daunting at first since it tries to do a lot of things that may or may not be needed for your application. After finishing this tutorial, you will be able to pick and choose what exactly is needed for your application and you can build a class with exactly that. Before we dive into this, we'll first take a quick look at how the startup process works at a high level.
The basic Ogre life cycle looks like this:
- Create the Root object.
- Define the resources that Ogre will use.
- Choose and set up the RenderSystem (that is, DirectX, OpenGL, etc).
- Create the RenderWindow (the window which Ogre resides in).
- Initialize the resources that you are going to use.
- Create a scene using those resources.
- Set up any third party libraries and plugins.
- Create any number of frame listeners.
- Start the render loop.
We will be going through each one of these in-depth in this tutorial.
I would like to note that while you really should do steps 1-4 in order, steps 5 and 6 (initializing resources and creating the scene) can come much later in your startup process if you like. You could initialize third party plugins and create frame listeners before steps 5 and 6 if you so choose, but you really shouldn't do them before finishing step 4. Also, the frame listeners/third party libraries cannot access any game related resources (such as your cameras, entities, etc) until the resources are initialized and the scene has been created. In short, you can do some of these steps out of order if you feel it will be better for your application, but I do not recommend doing so unless you are sure you understand the consequences of what you are doing.
Starting up Ogre
Creating the Root Object
The first thing we need to do is the simplest. The Root object is the core of the Ogre library, and must be created before you can do almost anything with the engine. Find the Application::createRoot function and add the following code:
mRoot = new Root();
That's all we need to do. The Root's constructor takes 3 parameters. The first is the name and location of the plugins config file. The second is the location of the ogre config file (which tells ogre things like the Video card, visual settings, etc). The last is the location and name of the log file that Ogre will write to. Since we don't really need to change any of these things, we will leave them as their default values.
Resources
Note: This section will make a lot more sense if you open up "resources.cfg" and take a look at it before continuing. You can find this in the bin/release folder of your SDK.
The next thing we have to do is define the resources that the application uses. This includes the textures, models, scripts, and so on. We will not be going over all there is to know about resources in this tutorial. For now, just keep in mind that you must first define all the resources that you might use during the application, then you must initialize the specific resources you need before Ogre can use them. In this step we are defining all resources that our application will possibly use.
To do this, we have to add each folder that resources reside in to the ResourceGroupManager. Find the defineResources function and add the following code:
String secName, typeName, archName;
ConfigFile cf;
cf.load("resources.cfg");
This uses Ogre's ConfigFile class to parse all of the resources from "resources.cfg", but this does not load them into Ogre (you have to do that manually). Keep in mind that in your own application you are free to use your own parser and config file formats if you like, just replace Ogre's ConfigFile parser with your own. The method for loading resources in doesn't really matter as long as you add the resources to the ResourceGroupManager. Now that we have parsed the config file, we need to add the sections to the ResourceGroupManager. The following code starts looping through the parsed config file:
ConfigFile::SectionIterator seci = cf.getSectionIterator();
while (seci.hasMoreElements())
{
For each section, we loop again, getting all the contents out of it:
secName = seci.peekNextKey();
ConfigFile::SettingsMultiMap *settings = seci.getNext();
ConfigFile::SettingsMultiMap::iterator i;
Finally, we add the section name (which is the group of the resources), the type of resource (zip, folder, etc), and finally the filename of the resource itself to the ResourceGroupManager:
for (i = settings->begin(); i != settings->end(); ++i)
{
typeName = i->first;
archName = i->second;
ResourceGroupManager::getSingleton().addResourceLocation(archName, typeName, secName);
}
}
This function we have now filled in adds all of the resources from the config file, but it only tells Ogre where they are. Before you can use any of them you have to either initialize the group that you want to use, or you must initialize them all. We will discuss this further in the "Initializing Resources" function.
Creating the RenderSystem
Next we need to choose the RenderSystem (usually either DirectX or OpenGL on a Windows machine) and then configure it. Most of the demo applications use the Ogre config dialog, which is a perfectly reasonable way to set up your application. Ogre also offers a way to restore the config that a user has already set, meaning you will never have to configure it after the first time. Find the setupRenderSystem function and add the following code:
if (!mRoot->restoreConfig() && !mRoot->showConfigDialog())
throw Exception(52, "User canceled the config dialog!", "Application::setupRenderSystem()");
In the first part of the if statement, we attempt to restore the config file. If that function returns false it means that the file does not exist so we should show the config dialog, which is the second portion of that if statement. If that also returns false it means the user canceled out of config dialog (meaning they want to exit the program). In this example, we have thrown an exception, but in practice I think it would probably be better to simply return false and close out the application that way, the reason being that the restoreConfig and showConfigDialog could possibly throw an exception as well, and it would be better to save exceptions for an actual failure. However, since making this change would needlessly complicate the tutorial, I've used an exception here.
If you use this technique in practice I would recommend that if you catch an exception during Ogre's startup that you delete the ogre.cfg file in the catch block. It is possible that the settings they have chosen in the config dialog has caused a problem and they need to change them. Even if you do not use this for your application when you distribute it to others, turning off the config dialog can help cut down on development time by a small amount since you do not have to keep confirming the graphics settings when the program runs.
Your application may also manually setup the RenderSystem if you choose to use something other than Ogre's config dialog. A basic example of this would be as follows:
// Do not add this to the application
RenderSystem *rs = mRoot->getRenderSystemByName("Direct3D9 Rendering Subsystem");
// or use "OpenGL Rendering Subsystem"
mRoot->setRenderSystem(rs);
rs->setConfigOption("Full Screen", "No");
rs->setConfigOption("Video Mode", "800 x 600 @ 32-bit colour");
You can use the Root::getAvailableRenderers to find out which RenderSystems are available for your application to use. Once you have retrieved a RenderSystem, you can use the RenderSystem::getConfigOptions to see what options are available for the user. By combining these two function calls, you can create your own config dialog for your application.
Creating a RenderWindow
Now that we have chosen the RenderSystem, we need a window to render Ogre in. There are actually a lot of options for how to do this, but we will really only cover a couple.
If you want Ogre to create a render window for you, then this is very easy to do. Find the createRenderWindow function and add the following code:
mRoot->initialise(true, "Tutorial Render Window");
This call initializes the RenderSystem we set in the previous section. The first parameter is whether or not Ogre should create a RenderWindow for you. Alternatively, you can create a render window yourself using the win32 API, wxWidgets, or one of the many other Windows or Linux GUI systems. A quick example of how to do this under Windows would look something like this:
// Do not add this to the application
mRoot->initialise(false);
HWND hWnd = 0; // Get the hWnd of the application!
NameValuePairList misc;
misc["externalWindowHandle"] = StringConverter::toString((int)hWnd);
RenderWindow *win = mRoot->createRenderWindow("Main RenderWindow", 800, 600, false, &misc);
Note that you still have to call Root::initialise, but the first parameter is set to false. Then, you must get the HWND of the window you want to render Ogre in. How you get this will be determined entirely by the GUI toolkit you use to create the window (and under Linux I would imagine this would be a bit different as well). After you have this, you use the NameValuePairList to assign the handle to "externalWindowHandle". The Root::createRenderWindow function then can be used to create the RenderWindow class from the window you have already created. Consult the API documentation on this function for more information.
Initializing Resources
Now that we have our Root object and our RenderSystem and RenderWindow objects created and ready to go, we are very close to being ready to create our scene. The only thing left to do is to initialize the resources we are about to use. In a very large game or application, we may have hundreds or even thousands of resources that our game uses - everything from meshes to textures to scripts. At any given time though, we probably will only be using a small subset of these resources. To keep down memory requirements, we can load only the resources that our application is using. We do this by dividing the resources into sections and only initializing them as we go. We will not be covering that in this tutorial, however. There is a full tutorial devoted to resources here.
Before we initialize the resources, we should also set the default number of mipmaps that textures use. We must set that before we initialize the resources for it to have any effect. Find the initializeResourceGroups function and add the following code:
TextureManager::getSingleton().setDefaultNumMipmaps(5);
ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
The application now has all resource groups initialized and ready to be used.
Creating a Scene
Next we will need to create the scene. We have done this many times in other tutorials, so we will not actually add anything to the scene we are about to create. Instead, you need to know that there are three things that must be done before you start adding things to a scene: creating the SceneManager, creating the Camera, and creating the Viewport. Find the setupScene function and add the following code:
SceneManager *mgr = mRoot->createSceneManager(ST_GENERIC, "Default SceneManager");
Camera *cam = mgr->createCamera("Camera");
Viewport *vp = mRoot->getAutoCreatedWindow()->addViewport(cam);
You may create as many SceneManagers as you like and as many Cameras as you like, but you have to be careful that when you actually want to render something on the screen using a Camera, you have to add a Viewport for it. You do this by using the RenderWindow class that was created in the "Creating a RenderWindow" section. Since we did not hold onto a pointer to this object, we access it through the Root::getAutoCreatedWindow function.
After these three things, you may now add whatever to your scene that you like.
Starting up Third Party Libraries
OIS
Though not the only option for input in Ogre, OIS is one of the best. We will briefly cover how to startup OIS in your application. For the actual uses of the library, you should check the various tutorials here (which use it extensively) and the OIS documentation itself.
Setup and Unbuffered Input
OIS uses a general InputManager which is a touch difficult to set up, but easy to use once you have created it properly. OIS does not integrate into Ogre; it's a standalone library, which means that you will need to provide it with some information at the beginning for it to work properly. In practice it really only needs the window handle which Ogre is rendering in. Thankfully, since we have used the auto created window, Ogre makes this easy for us. Find the setupInputSystem function and add the following code:
size_t windowHnd = 0;
std::ostringstream windowHndStr;
OIS::ParamList pl;
RenderWindow *win = mRoot->getAutoCreatedWindow();
win->getCustomAttribute("WINDOW", &windowHnd);
windowHndStr << windowHnd;
pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str()));
mInputManager = OIS::InputManager::createInputSystem(pl);
This sets up the InputManager for use, but to actually use OIS to get input for the Keyboard, Mouse, or Joystick of your choice, you'll need to create those objects:
try
{
mKeyboard = static_cast<OIS::Keyboard*>(mInputManager->createInputObject(OIS::OISKeyboard, false));
//mMouse = static_cast<OIS::Mouse*>(mInputManager->createInputObject(OIS::OISMouse, false));
//mJoy = static_cast<OIS::JoyStick*>(mInputManager->createInputObject(OIS::OISJoyStick, false));
}
catch (const OIS::Exception &e)
{
throw Exception(42, e.eText, "Application::setupInputSystem");
}
I've commented out the Mouse and Joystick objects since we will not use them in this application, but that is how you create them. The second parameter to InputManager::createInputObject is whether or not to use buffered input (which we discussed in the last two tutorials). Setting the second parameter to false creates an unbuffered input object, which we are using in this tutorial.
Note: If you wish to use buffered input (meaning you get event callbacks to mouseMoved, mousePressed, keyReleased, and so on), then you must set the second parameter to createInputObject to be true.
Setting Up the Framelistener
No matter if you are using buffered or unbuffered input, every frame you must call the capture method on all Keyboard, Mouse, and Joystick objects you use. In this tutorial, the starting code already has done this. For unbuffered input, this is all you need to do. Every frame you can call the various Keyboard and Mouse functions to query for the state of these objects. For buffered input, we have slightly more work to do.
To use buffered input, you need to add a class to handle the input (or you could just add code to the FrameListener you have created). The two things you need to do to set up buffered input (aside from passing true to the createInputObject method) is to implement the appropriate listener interfaces and to register the class you've created as the event callback. You can see examples of doing this in the previous tutorial specifically about buffered input. Here is a class which implements everything:
// Don't add this to the project
class BufferedInputHandler : public OIS::KeyListener, public OIS::MouseListener, public OIS::JoyStickListener
{
public:
BufferedInputHandler(OIS::Keyboard *keyboard = 0, OIS::Mouse *mouse = 0, OIS::JoyStick *joystick = 0)
{
if (keyboard)
keyboard->setEventCallback(this);
if (mouse)
mouse->setEventCallback(this);
if (joystick)
joystick->setEventCallback(this);
}
// KeyListener
virtual bool keyPressed(const OIS::KeyEvent &arg) { return true; }
virtual bool keyReleased(const OIS::KeyEvent &arg) { return true; }
// MouseListener
virtual bool mouseMoved(const OIS::MouseEvent &arg) { return true; }
virtual bool mousePressed(const OIS::MouseEvent &arg, OIS::MouseButtonID id) { return true; }
virtual bool mouseReleased(const OIS::MouseEvent &arg, OIS::MouseButtonID id) { return true; }
// JoystickListener
virtual bool buttonPressed(const OIS::JoyStickEvent &arg, int button) { return true; }
virtual bool buttonReleased(const OIS::JoyStickEvent &arg, int button) { return true; }
virtual bool axisMoved(const OIS::JoyStickEvent &arg, int axis) { return true; }
};
I recommend that you implement only the listeners that you actually use though.
Troubleshooting Buffered Input
If you are not receiving buffered input like you think you should in your application, then there are a few things you should check before doing anything else:
- Do the calls to InputManager::createInputObject set the buffered input flag (second parameter) to true?
- Have you called setEventCallback on each buffered input object?
- Are any other classes calling setEventCallback? (Note that OIS only allows one event callback. You cannot register more than one event callback for it.)
If you have checked these three things, post your problem to the help forums.
CEGUI
CEGUI is a very flexible GUI library which integrates into Ogre directly. While we will not be using any of CEGUI's functionality in this application, I will briefly go over how to set it up for future applications. CEGUI requires the RenderWindow and the SceneManager which it will render in. (NOTE: to correctly use CEGUI and compile the project source without error messages, add CEGUIBase_d.lib OgreGUIRenderer_d.lib to Linker>Input>Additional Dependencies)
Insert this code into the setupCEGUI function:
mRenderer = &CEGUI::OgreRenderer::bootstrapSystem();
That's it. Now CEGUI is ready for you to use.
Finalizing Startup and the Render Loop
Frame Listener
Before we start the render loop and have our application run, we need to add the frame listeners that the application will use. Please note that I have already created a very simple FrameListener called ExitListener, which waits for the Escape key to be pressed to end the program. In your program you will probably have a lot more frame listeners doing much more complex things. Take a look at the ExitListener and make sure you understand everything that is going on there, then add the following code to the createFrameListener function:
mListener = new ExitListener(mKeyboard);
mRoot->addFrameListener(mListener);
The Render Loop
The last thing we need to do to start Ogre is to start the render loop. Ogre makes this very easy for us. Find the startRenderLoop function and add the following code:
mRoot->startRendering();
This will render the application until a FrameListener returns false. You can also pump a single frame and work in between each frame if you so choose. The Root::renderOneFrame renders a frame and then returns false if any FrameListener returned false:
// Do not add this to the application
while (mRoot->renderOneFrame())
{
// Do some things here, like sleep for x milliseconds or perform other actions.
// However, make sure you call the display update function:
WindowEventUtilities::messagePump();
}
However, in my opinion you should probably just add whatever code you would have put in that while loop to a FrameListener instead. The only use I can think of to use this pattern is to sleep for a certain number of milliseconds to artificially lower the framerate down to a set number. You generally wouldn't want to do that in a FrameListener because it messes with the FrameEvent::timeSinceLastFrame variable.
Optional Handling of Window Events
If you wish to intercept certain Window Events, you can add a WindowEventListener instead of manually checking in a message pump loop as shown above. For example:
// Do not add this to the application
class WinListener : public WindowEventListener
{
public:
WinListener()
{
}
bool windowClosing(RenderWindow* rw)
{
// Disables standard exit methods such as ALT-F4 and the close button
return false;
}
};
And then do this before calling mRoot->startRendering():
// Do not add this to the application
SceneManager *mgr = mRoot->getSceneManager("Default SceneManager");
RenderWindow *win = mRoot->getAutoCreatedWindow();
WindowEventUtilities::addWindowEventListener(win, new WinListener());
Cleanup
The last thing we have to worry about is cleaning up all of the objects we have created when the application terminates. To do this, we will basically delete or destroy all objects in the reverse order of their creation. We will start with OIS, which has specific functions that should be called to destroy its objects. Find the ~Application function and add the following code:
mInputManager->destroyInputObject(mKeyboard);
OIS::InputManager::destroyInputSystem(mInputManager);
CEGUI:
CEGUI::OgreRenderer::destroySystem();
Finally we need to delete the Root and FrameListener objects. The other objects we have created (the SceneManager, the RenderWindow and so on) will be cleaned up when we delete Root.
delete mListener;
delete mRoot;
That's it! You can now compile and run your application, though you will only see a black screen since we never added anything to the scene. If you have linker errors when attempting to compile the application, be sure that CEGUIBase_d.lib and OgreGUIRenderer_d.lib are added to the input of the linker (for debug mode, for release mode, remove the _d on them). You should now be familiar enough with Ogre's startup process to use something other than the example framework in your applications. For the sake of simplicity, however, the tutorials will continue to use the example framework.
If you are curious about other parts of the example framework and how it works, see the in-depth tutorial Example Framework Demystified.
Mac OS X
Since Mac OS X uses app bundles, a concept radically different from what is used on Windows and Linux, the code described above is going to crash on Mac OS X.
- Add the following function:
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
#include <CoreFoundation/CoreFoundation.h>
// This function will locate the path to our application on OS X,
// unlike windows you cannot rely on the current working directory
// for locating your configuration files and resources.
std::string macBundlePath()
{
char path[1024];
CFBundleRef mainBundle = CFBundleGetMainBundle();
assert(mainBundle);
CFURLRef mainBundleURL = CFBundleCopyBundleURL(mainBundle);
assert(mainBundleURL);
CFStringRef cfStringRef = CFURLCopyFileSystemPath( mainBundleURL, kCFURLPOSIXPathStyle);
assert(cfStringRef);
CFStringGetCString(cfStringRef, path, 1024, kCFStringEncodingASCII);
CFRelease(mainBundleURL);
CFRelease(cfStringRef);
return std::string(path);
}
#endif
- In createRoot(), change
mRoot = new Root();
to
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
mRoot = new Root(macBundlePath() + "/Contents/Resources/plugins.cfg");
#else
mRoot = new Root();
#endif
- In defineResources(), change
cf.load("resources.cfg");
to
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
cf.load(macBundlePath() + "/Contents/Resources/resources.cfg");
#else
cf.load("resources.cfg");
#endif
- Also in defineResources(), change
ResourceGroupManager::getSingleton().addResourceLocation( archName, typeName, secName);
to
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
ResourceGroupManager::getSingleton().addResourceLocation( String(macBundlePath() + "/" + archName), typeName, secName);
#else
ResourceGroupManager::getSingleton().addResourceLocation( archName, typeName, secName);
#endif
- Proceed to Basic Tutorial 7 CEGUI and Ogre
| Ogre Tutorials |
|---|
|
Ogre Beginner Tutorials: 1. Basic Introduction - 2. Cameras, Lights and Shadows - 3. Terrain, Sky and Fog - 4. Frame Listeners and Unbuffered Input - 5. Buffered Input - 6. The Ogre Startup Sequence - 7. CEGUI and OGRE - 8. Multiple and Dual SceneManagers Intermediate Tutorials: 1. Animation, Interpolation and Quaternions - 2. RaySceneQueries and Basic Mouse Usage (1/2) - 3. Mouse Picking and SceneQuery Masks (2/2) - 4. Volume Selection and Manual Objects - 5. Static Geometry - 6. Projective Decals - 7. Render to Texture Advanced Tutorials: 1. Resources and ResourceManagers See also: Artist Tutorials - Ogre Articles - Cookbook |

