This is a very minimal application that does the same thing that the Beginner Tutorial #1 does: pops up a window with nothing in it and waits for you to hit ESCAPE, at which point it exits.
To this point we have examined how the major parts of a practical Ogre-based application would be constructed, and implementation issues with each. Now, we will go ahead and start writing one. Let's start with the basics. We'll start with an uninterrupted code listing.
Table of contents
The Code
main()
main.cpp
#include "input.h" #include "simulation.h" #include "Ogre.h" #include "OgreWindowEventUtilities.h" #if defined(WIN32) #include "windows.h" int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { #else int main (int argc, char *argv[]) { #endif Ogre::Root *ogre; Ogre::RenderWindow *window; Ogre::SceneManager *sceneMgr; Ogre::Camera *camera; // fire up an Ogre rendering window. Clearing the first two (of three) params will let us // specify plugins and resources in code instead of via text file ogre = new Ogre::Root("", ""); // This is a VERY minimal rendersystem loading example; we are hardcoding the OpenGL // renderer, instead of loading GL and D3D9. We will add renderer selection support in a // future article. // I separate the debug and release versions of my plugins using the same "_d" suffix that // the Ogre main libraries use; you may need to remove the "_d" in your code, depending on the // naming convention you use // EIHORT NOTE: All Ogre DLLs use this suffix convention now -- #ifdef on the basis of the _DEBUG // define #if defined(_DEBUG) ogre->loadPlugin("RenderSystem_GL_d"); #else ogre->loadPlugin("RenderSystem_GL"); #endif /* ---------------------------------------------------------------------------- [Note]: If using Ogre 1.7 the next 4 lines of code (not including comments) should be changed to the following 2 lines: const Ogre::RenderSystemList &renderSystem = ogre->getAvailableRenderers(); Ogre::RenderSystemList::const_iterator &r_it = renderSystem.begin(); If using Ogre 1.9 the second line becomes: Ogre::RenderSystemList::const_iterator r_it = renderSystem.begin(); [End Note] ---------------------------------------------------------------------------- */ Ogre::RenderSystemList *renderSystems = NULL; Ogre::RenderSystemList::iterator r_it; // we do this step just to get an iterator that we can use with setRenderSystem. In a future article // we actually will iterate the list to display which renderers are available. renderSystems = ogre->getAvailableRenderers(); r_it = renderSystems->begin(); ogre->setRenderSystem(*r_it); ogre->initialise(false); // load common plugins #if defined(_DEBUG) ogre->loadPlugin("Plugin_CgProgramManager_d"); ogre->loadPlugin("Plugin_OctreeSceneManager_d"); #else ogre->loadPlugin("Plugin_CgProgramManager"); ogre->loadPlugin("Plugin_OctreeSceneManager"); #endif // load the basic resource location(s) Ogre::ResourceGroupManager::getSingleton().addResourceLocation( "resource", "FileSystem", "General"); Ogre::ResourceGroupManager::getSingleton().addResourceLocation( "resource/gui.zip", "Zip", "GUI"); #if defined(WIN32) Ogre::ResourceGroupManager::getSingleton().addResourceLocation( "c:\\windows\\fonts", "FileSystem", "GUI"); #endif Ogre::ResourceGroupManager::getSingleton().initialiseResourceGroup("General"); Ogre::ResourceGroupManager::getSingleton().initialiseResourceGroup("GUI"); // setup main window; hardcode some defaults for the sake of presentation Ogre::NameValuePairList opts; opts["resolution"] = "1024x768"; opts["fullscreen"] = "false"; opts["vsync"] = "false"; // create a rendering window with the title "CDK" window = ogre->createRenderWindow("CDK", 1024, 768, false, &opts); // since this is basically a CEGUI app, we can use the ST_GENERIC scene manager for now; in a later article // we'll see how to change this sceneMgr = ogre->createSceneManager(Ogre::ST_GENERIC); camera = sceneMgr->createCamera("camera"); camera->setNearClipDistance(5); Ogre::Viewport* vp = window->addViewport(camera); vp->setBackgroundColour(Ogre::ColourValue(0,0,0)); // most examples get the viewport size to calculate this; for now, we'll just // set it to 4:3 the easy way camera->setAspectRatio((Ogre::Real)1.333333); // this next bit is for the sake of the input handler unsigned long hWnd; window->getCustomAttribute("WINDOW", &hWnd); // set up the input handlers Simulation *sim = new Simulation(); InputHandler *handler = new InputHandler(sim, hWnd); sim->requestStateChange(SIMULATION); while (sim->getCurrentState() != SHUTDOWN) { handler->capture(); // run the message pump (Eihort) Ogre::WindowEventUtilities::messagePump(); ogre->renderOneFrame(); } // clean up after ourselves delete handler; delete sim; delete ogre; return 0; }
I have lifted this code from a content-development kit I am creating for our project, hence the name "CDK". I have stripped out all of the actual useful, "fun" code in the intersts of laying bare the essentials; we'll add more later: by the end of this track, we will have an app that can move a model around a scene.
A quick tour of the code shows the initialization, resource location setup, and the main loop. The main loop, as discussed earlier, also calls the windowing system's message pump, which on Win32 enables the rendering window to be "well-behaved" (meaning, it responds well to system key combinations such as ALT+TAB, among other features). As we'll find later, it also enables the window to display its contents: without this message pump, the Ogre render window code does not get "paint" messages (WM_PAINT in Win32, which enables the window to refresh its contents regularly).
There are a few other items to take care of before building this code. First, there are a few other files we need to see. First, the "Simulation" declaration (.h) and definition (.cpp) files:
Simulation
simulation.h
#pragma once #include <vector> #include <map> typedef enum { STARTUP, GUI, LOADING, CANCEL_LOADING, SIMULATION, SHUTDOWN } SimulationState; class Simulation { public: Simulation(); virtual ~Simulation(); public: bool requestStateChange(SimulationState state); bool lockState(); bool unlockState(); SimulationState getCurrentState(); void setFrameTime(float ms); inline float getFrameTime() { return m_frame_time; } protected: SimulationState m_state; bool m_locked; float m_frame_time; };
simulation.cpp
#include "simulation.h" #include "OgreStringConverter.h" Simulation::Simulation() { m_state = STARTUP; } Simulation::~Simulation() { } SimulationState Simulation::getCurrentState() { return m_state; } // for the sake of clarity, I am not using actual thread synchronization // objects to serialize access to this resource. You would want to protect // this block with a mutex or critical section, etc. bool Simulation::lockState() { if (m_locked == false) { m_locked = true; return true; } else return false; } bool Simulation::unlockState() { if (m_locked == true) { m_locked = false; return true; } else return false; } bool Simulation::requestStateChange(SimulationState newState) { if (m_state == STARTUP) { m_locked = false; m_state = newState; return true; } // this state cannot be changed once initiated if (m_state == SHUTDOWN) { return false; } if ((m_state == GUI || m_state == SIMULATION || m_state == LOADING || m_state == CANCEL_LOADING) && (newState != STARTUP) && (newState != m_state)) { m_state = newState; return true; } else return false; } void Simulation::setFrameTime(float ms) { m_frame_time = ms; }
This "Simulation" class is a very simple example of a "state manager" class. Simulation (or game) states are nothing more than different execution contexts, and in the interests of orderly execution of your program, you will use them too. We show more states than we need here, but those states are all used in our project. There are no "official" game states; a state is just whatever you define it to be, however you intend to use it, it's game-specific. However, there are typically some very common states that all games and simulations will share, and SHUTDOWN, SIMULATION and GUI are three typical states. SHUTDOWN informs the app that a shutdown has been requested (in our case, either by the user pressing ESCAPE or clicking the "X" in the upper-right corner of the rendering window). SIMULATION is the state defined by "normal" execution of our game (we are running the simulation or processing game logic, updating and displaying the 3D scene, etc). GUI is a state of user interaction with a 2D GUI, such as that supported by CEGUI. (We have a separate GUI state to be able to tell when NOT to inject input into CEGUI, to prevent unnecessarily wasting CPU cycles).
The input handler in our example uses OIS. We leverage OIS in the input.h/.cpp files:
InputHandler
input.h
#pragma once #include "OISEvents.h" #include "OISInputManager.h" #include "OISMouse.h" #include "OISKeyboard.h" #include "OISJoyStick.h" class Simulation; class InputHandler : public OIS::MouseListener, public OIS::KeyListener, public OIS::JoyStickListener { private: OIS::InputManager *m_ois; OIS::Mouse *mMouse; OIS::Keyboard *mKeyboard; unsigned long m_hWnd; Simulation *m_simulation; public: InputHandler(Simulation *sim, unsigned long hWnd); ~InputHandler(); void setWindowExtents(int width, int height) ; void capture(); // MouseListener bool mouseMoved(const OIS::MouseEvent &evt); bool mousePressed(const OIS::MouseEvent &evt, OIS::MouseButtonID); bool mouseReleased(const OIS::MouseEvent &evt, OIS::MouseButtonID); // KeyListener bool keyPressed(const OIS::KeyEvent &evt); bool keyReleased(const OIS::KeyEvent &evt); // JoyStickListener bool buttonPressed(const OIS::JoyStickEvent &evt, int index); bool buttonReleased(const OIS::JoyStickEvent &evt, int index); bool axisMoved(const OIS::JoyStickEvent &evt, int index); bool povMoved(const OIS::JoyStickEvent &evt, int index); };
input.cpp
#include "input.h" #include "OgreStringConverter.h" #include "simulation.h" InputHandler::InputHandler(Simulation *sim, unsigned long hWnd) { OIS::ParamList pl; pl.insert(OIS::ParamList::value_type("WINDOW", Ogre::StringConverter::toString(hWnd))); m_hWnd = hWnd; m_ois = OIS::InputManager::createInputSystem( pl ); mMouse = static_cast<OIS::Mouse*>(m_ois->createInputObject( OIS::OISMouse, true )); mKeyboard = static_cast<OIS::Keyboard*>(m_ois->createInputObject( OIS::OISKeyboard, true)); mMouse->setEventCallback(this); mKeyboard->setEventCallback(this); m_simulation = sim; } InputHandler::~InputHandler() { if (mMouse) m_ois->destroyInputObject(mMouse); if (mKeyboard) m_ois->destroyInputObject(mKeyboard); OIS::InputManager::destroyInputSystem(m_ois); } void InputHandler::capture() { mMouse->capture(); mKeyboard->capture(); } void InputHandler::setWindowExtents(int width, int height){ //Set Mouse Region.. if window resizes, we should alter this to reflect as well const OIS::MouseState &ms = mMouse->getMouseState(); ms.width = width; ms.height = height; } // MouseListener bool InputHandler::mouseMoved(const OIS::MouseEvent &evt) { return true; } bool InputHandler::mousePressed(const OIS::MouseEvent &evt, OIS::MouseButtonID btn) { return true; } bool InputHandler::mouseReleased(const OIS::MouseEvent &evt, OIS::MouseButtonID btn) { return true; } // KeyListener bool InputHandler::keyPressed(const OIS::KeyEvent &evt) { return true; } bool InputHandler::keyReleased(const OIS::KeyEvent &evt) { if (evt.key == OIS::KC_ESCAPE) m_simulation->requestStateChange(SHUTDOWN); return true; } // JoyStickListener bool InputHandler::buttonPressed(const OIS::JoyStickEvent &evt, int index) { return true; } bool InputHandler::buttonReleased(const OIS::JoyStickEvent &evt, int index) { return true; } bool InputHandler::axisMoved(const OIS::JoyStickEvent &evt, int index) { return true; } bool InputHandler::povMoved(const OIS::JoyStickEvent &evt, int index) { return true; }
Since the ESCAPE key press is handled in a separate class, we have to let the application know somehow that it should shut down. We do this via the simulation state, and in order to do that, we need to have a reference to the simulation controller object in the input handler. We do this by passing a pointer to the object in the input handler constructor, and storing it in as a class data member.
This is actually a fairly important point, done in microcosm for clarity. Often you will see "Singleton" objects in Ogre. They are class instances, of which there can only ever be one (details on the Singleton pattern are better handled elsewhere).
Notice that we don't just change state, we "request to change state". If arbitrary parts of your program just stomped all over the state of the program, what happens to code that is in the middle of running in the context of a particular state? This is more an issue with multithreaded programs than single-threaded, and non-trivial programs often are multithreaded. If we allow state change "requests" instead of just slamming it home, as it were, we allow sections of code to complete their operation gracefully, and THEN the state can be changed. In this trivial example, of course, we just change the state on request, but we do leave ourselves the option for more elegant state management should it be necessary.
We operate OIS in buffered mode so that we can avoid missing input events. Calling InputHandler::capture() in turn calls the mouse and keyboard capture() methods, which will empty their buffers of pending events and distribute them accordingly.
I left the joystick event handler methods in there to show how joystick support is implemented. We are short only a single data member (joystick device pointer) of having stick support as well. We don't need it now, but it will be implemented in a later article.
Building and Running the Code
Ogre::ResourceGroupManager::getSingleton().addResourceLocation( "resource", "FileSystem", "General"); Ogre::ResourceGroupManager::getSingleton().addResourceLocation( "resource/gui.zip", "Zip", "GUI");
As you can see in the initialization section, we define and populate two resource groups: General and GUI. General actually is always present, and is the default resource group for Ogre. GUI is a group we create to hold GUI content, and everything in the gui.zip file is loaded into this group.
In order for this to work without crashing, you must have a directory called "resource" containing a zip file called "gui.zip" in the working directory of the app. If you grab the ZIP file at the end of this article, you will find these in the Debug/ folder after unpacking. These can be anywhere you want; as you can see, we reference the Windows "fonts" directory as well (we'll see why in the next article).
We also need to have the proper include and library directories set up for this to build. You'll need to grab OIS from the SourceForge page above (unless you use the ZIP at the end of the article, which includes OIS), and obviously you'll need the Ogre headers and libraries as well (which come with the Ogre SDK). You'll also need the usual Ogre dependencies listed on the Ogre site if you are building Ogre from source (the Ogre SDK includes the needed dependency DLLs and headers).
I assume you know how to set directories in Visual C++ (or your particular IDE); if not, visit the docs for your IDE of choice and then come back. If you have installed the Ogre SDK, then you do not need to do anything else. If you have built from source, you'll need the following:
C++:
Additional Include Directories: (paths to the Ogre headers and the OIS headers)
Linker:
Additional Library Directories: (paths to the Debug Ogre .lib files and the Debug OIS static library) Input: OgreMain_d.lib OIS_d.lib dxguid.lib dinput8.lib
You will also need to set the Working Directory under "Debugging" to point to the Debug/ folder. I've not included the .suo that inexplicably contains this per-project setting; if you get errors trying to execute that complain of not being able to find the "resource" directory, this is the problem: set the working directory properly and all should be well.
We need the DX libs because OIS builds as a static library and it has dependency on these libs. Note that we are using the Debug versions of the libs; remove the "_d" if you want the release.
The VC++ project is just a normal empty Win32 project. Alternately, you can unpack the ZIP below and just build it (after fixing up the directories mentioned above to reflect your setup). I used VC++ 2003 (VC 7.1) to build this code.
Sample Code Package
Download the code for this article
[NOTE: This code will still compile and execute with Ogre 1.7. It will work as advertised (that is a blank screen you can hit escape to exit from). The major changes occur when dealing with CEGUI and are covered in the next section.]
Building and Execution Notes
- This code has been updated for Eihort, and was tested against the Ogre SDK 1.4RC1 (Eihort) for VC71; this means it will build and run against any Ogre 1.4.x SDK installation.
- The ZIP file has further been updated to Ogre 1.6.x and includes VC9 sln/vcproj files
- The code download is ~1MB in a ZIP file. It contains the source from this article; since Eihort provides the proper version of OIS as a Dependency, it no longer is needed in the source distribution for this article.
- You will need to install the SDK in order to build this code (unless you have built Ogre from source and don't mind hacking around in the project files to point include and lib directories to the proper places). If you have installed the Ogre SDK 1.4.x, you should be able simply to unzip this ZIP file, open the PracticalApp.sln, click "Build Solution" for the Debug configuration and have it complete with no problems.
- You will want to add $OGRE_HOME/bin/Debug to your PATH before running the PracticalApp.exe executable; how you do this is up to you (when testing, I opened a Cygwin Bash shell and typed "export PATH=$OGRE_HOME\bin\Debug:$PATH" before running PracticalApp.exe from the Bash prompt — your mileage may vary; you can just as easily add it to the Windows environment variables if you want). This is because the Ogre libs are "over there", and the tutorial executable is "over here" (along with the resource/ directory it expects to have in the same directory as the executable).
- You can also set it directly in the project settings under Debugging; any environment variables you set here will be merged with the environment that Visual Studio has. The VC9 project files also include a .vcproj.user file that contains these settings, so if you have installed the SDK or have set OGRE_HOME properly, it will *just work* out of the box.
- Additional notes regarding building the source code with Visual Studio 2005 Pro are in the discussions section of this article
Prev <<< Practical Application - GUI | Practical Application - Something With A Bit More Meat >>> Next |