Basic Tutorial 6
From PyWiki
Beginner Tutorial 6: The Ogre Startup Sequence
Prerequisites
This tutorial assumes you have knowledge of Python programming and you have already installed Python-Ogre. This tutorial builds on the material covered in previous tutorials, and it assumes you have read them.
Introduction
In this tutorial you will learn how to startup Ogre without using the example framework. By the time you are through 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:
import ogre.renderer.OGRE as ogre import ogre.io.OIS as OIS import ogre.gui.CEGUI as CEGUI class ExitListener(ogre.FrameListener): def __init__(self, keyboard): ogre.FrameListener.__init__(self) self.keyboard = keyboard def frameStarted(self, evt): self.keyboard.capture() return not self.keyboard.isKeyDown(OIS.KC_ESCAPE) def __del__(self): pass class Application(object): def go(self): self.createRoot() self.defineResources() self.setupRenderSystem() self.createRenderWindow() self.initializeResourceGroups() self.setupScene() self.setupInputSystem() self.setupCEGUI() self.createFrameListener() self.startRenderLoop() self.cleanUp() def createRoot(self): pass def defineResources(self): pass def setupRenderSystem(self): pass def createRenderWindow(self): pass def initializeResourceGroups(self): pass def setupScene(self): pass def setupInputSystem(self): pass def setupCEGUI(self): pass def createFrameListener(self): pass def startRenderLoop(self): pass def cleanUp(self): pass if __name__ == '__main__': try: ta = Application() ta.go() except ogre.OgreException, e: print e
Be sure you can compile this code before continuing.
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:
1. Create the Root object. 2. Define the resources that Ogre will use. 3. Choose and setup the RenderSystem (that is, DirectX, OpenGL, etc). 4. Create the RenderWindow (the window which Ogre resides in). 5. Initialize the resources that you are going to use. 6. Create a scene using those resources. 7. Setup any third party libraries and plugins. 8. Create any number of frame listeners. 9. 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 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:
self.root = ogre.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 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 Application.defineResources function and add the following code:
cf = ogre.ConfigFile()
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:
seci = cf.getSectionIterator()
while seci.hasMoreElements():
For each section, we loop again, getting all the contents out of it:
secName = seci.peekNextKey()
settings = seci.getNext()
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 item in settings:
typeName = item.key
archName = item.value
ogre.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 setup 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 Application.setupRenderSystem function and add the following code:
if not self.root.restoreConfig() and not self.root.showConfigDialog():
raise Exception("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 = self.root.getRenderSystemByName("Direct3D9 Rendering Subsystem")
self.root.setRenderSystem(renderSystem);
renderSystem.setConfigOption("Full Screen", "No")
renderSystem.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 Application.createRenderWindow function and add the following code:
self.root.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
self.root.initialise(False)
hWnd = 0 # Get the hWnd of the application!
misc = ogre.NameValuePairList()
misc["externalWindowHandle"] = str(int(hWnd))
renderWindow = self.root.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 affect. Find the Application.initializeResourceGroups function and add the following code:
ogre.TextureManager.getSingleton().setDefaultNumMipmaps(5)
ogre.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 Application.setupScene function and add the following code:
sceneManager = self.root.createSceneManager(ogre.ST_GENERIC, "Default SceneManager")
camera = sceneManager.createCamera("Camera")
viewPort = self.root.getAutoCreatedWindow().addViewport(camera)
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 Application.setupInputSystem function and add the following code:
renderWindow = self.root.getAutoCreatedWindow()
windowHandle = renderWindow.getCustomAttributeInt("WINDOW")
paramList = [("WINDOW", str(windowHandle))]
self.inputManager = OIS.createPythonInputSystem(paramList)
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:
self.keyboard = self.inputManager.createInputObjectKeyboard(OIS.OISKeyboard, False)
# self.mouse = self.inputManager.createInputObjectMouse(OIS.OISMouse, False)
# self.joystick = self.inputManager.createInputObjectJoyStick(OIS.OISJoyStick, False)
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 setup 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(ogre.FrameListener, OIS.KeyListener, OIS.MouseListener, OIS.JoyStickListener):
def __init__(self, keyboard = 0, mouse = 0, joystick = 0):
# Calling the __init__ methods of the OIS superclasses is required to make sure everything works
# You will also need to store references to instances of this class to prevent them being garbage collected
OIS.KeyListener.__init__(self)
OIS.MouseListener.__init__(self)
OIS.JoyStickListener.__init__(self)
if keyboard:
keyboard.setEventCallback(self)
if mouse:
mouse.setEventCallback(self)
if joystick:
joystick.setEventCallback(self)
# KeyListener
def keyPressed(self, evt):
return True
def keyReleased(self, evt):
return True
# MouseListener
def mouseMoved(self, evt):
return True
def mousePressed(self, evt, id):
return True
def mouseReleased(self, evt, id):
return True
# JoystickListener
def buttonPressed(self, evt, button):
return True
def buttonReleased(self, evt, button):
return True
def axisMoved(self, evt, 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:
1. Do the calls to InputManager.createInputSystem set the buffered input flag (second
parameter) to True?
2. Have you called setEventCallback on each buffered input system?
3. 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
The new version of python-ogre comes with newer version of CEGUI. The syntax has changed a little in the newer version. Because some of the users of this tutorial might have the older version of CEGUI still, a version checker has been implemented to get CEGUI working properly.
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. Find the Application.setupCEGUI function and add the following code:
sceneManager = self.root.getSceneManager("Default SceneManager")
renderWindow = self.root.getAutoCreatedWindow()
# CEGUI setup
if CEGUI.Version__.startswith("0.6"):
self.renderer = CEGUI.OgreCEGUIRenderer(renderWindow, ogre.RENDER_QUEUE_OVERLAY, False, 3000, sceneManager)
self.system = CEGUI.System(self.renderer)
else:
self.renderer = CEGUI.OgreRenderer.bootstrapSystem()
self.system = CEGUI.System.getSingleton()
That's it. Now CEGUI is ready for you to use. Note that if you change SceneManagers during the application (and you are using CEGUI), you will have to inform CEGUI that it should be rendering to a new SceneManager. The OgreCEGUIRenderer.setTargetSceneManager is how you change this.
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 Application.createFrameListener function:
self.exitListener = ExitListener(self.keyboard)
self.root.addFrameListener(self.exitListener)
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 Application.startRenderLoop function and add the following code:
self.root.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 self.root.renderOneFrame():
# Do some things here, like sleep for x milliseconds or perform other actions.
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.
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.cleanUp function and add the following code:
self.inputManager.destroyInputObjectKeyboard(self.keyboard)
# self.inputManager.destroyInputObjectMouse(self.mouse)
# self.inputManager.destroyInputObjectJoyStick(self.joystick)
OIS.InputManager.destroyInputSystem(self.inputManager)
self.inputManager = None
When we cleanup CEGUI, we simply need to delete the objects. From here we'll switch to the object's destructor, so find Application.__del__ and add the following code:
del self.renderer
del self.system
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.
del self.exitListener
del self.root
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. 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.
Complete Source Code Example
import ogre.renderer.OGRE as ogre import ogre.io.OIS as OIS import ogre.gui.CEGUI as CEGUI class ExitListener(ogre.FrameListener): def __init__(self, keyboard): ogre.FrameListener.__init__(self) self.keyboard = keyboard def frameStarted(self, evt): self.keyboard.capture() return not self.keyboard.isKeyDown(OIS.KC_ESCAPE) def __del__(self): del self.renderer del self.system del self.exitListener del self.root class Application(object): def go(self): self.createRoot() self.defineResources() self.setupRenderSystem() self.createRenderWindow() self.initializeResourceGroups() self.setupScene() self.setupInputSystem() self.setupCEGUI() self.createFrameListener() self.startRenderLoop() self.cleanUp() # The Root constructor for the ogre def createRoot(self): self.root = ogre.Root() # Here the resources are read from the resources.cfg def defineResources(self): cf = ogre.ConfigFile() cf.load("resources.cfg") seci = cf.getSectionIterator() while seci.hasMoreElements(): secName = seci.peekNextKey() settings = seci.getNext() for item in settings: typeName = item.key archName = item.value ogre.ResourceGroupManager.getSingleton().addResourceLocation(archName, typeName, secName) # Create and configure the rendering system (either DirectX or OpenGL) here def setupRenderSystem(self): if not self.root.restoreConfig() and not self.root.showConfigDialog(): raise Exception("User canceled the config dialog -> Application.setupRenderSystem()") # Create the render window def createRenderWindow(self): self.root.initialise(True, "Tutorial Render Window") # Initialize the resources here (which were read from resources.cfg in defineResources() def initializeResourceGroups(self): ogre.TextureManager.getSingleton().setDefaultNumMipmaps(5) ogre.ResourceGroupManager.getSingleton().initialiseAllResourceGroups() # Now, create a scene here. Three things that MUST BE done are sceneManager, camera and # viewport initializations def setupScene(self): sceneManager = self.root.createSceneManager(ogre.ST_GENERIC, "Default SceneManager") camera = sceneManager.createCamera("Camera") viewPort = self.root.getAutoCreatedWindow().addViewport(camera) # here setup the input system (OIS is the one preferred with Ogre3D) def setupInputSystem(self): windowHandle = 0 renderWindow = self.root.getAutoCreatedWindow() windowHandle = renderWindow.getCustomAttributeInt("WINDOW") paramList = [("WINDOW", str(windowHandle))] self.inputManager = OIS.createPythonInputSystem(paramList) # Now InputManager is initialized for use. Keyboard and Mouse objects # must still be initialized separately try: self.keyboard = self.inputManager.createInputObjectKeyboard(OIS.OISKeyboard, False) self.mouse = self.inputManager.createInputObjectMouse(OIS.OISMouse, False) except Exception, e: raise e # CEGUI library is used for creating graphical user interfaces (options menus, etc) def setupCEGUI(self): sceneManager = self.root.getSceneManager("Default SceneManager") renderWindow = self.root.getAutoCreatedWindow() # CEGUI setup # The newer version of CEGUI has different syntax, so this tutorial code results # in runnable program when used if CEGUI.Version__.startswith("0.6"): self.renderer = CEGUI.OgreCEGUIRenderer(renderWindow, ogre.RENDER_QUEUE_OVERLAY, False, 3000, sceneManager) self.system = CEGUI.System(self.renderer) else: self.renderer = CEGUI.OgreRenderer.bootstrapSystem() self.system = CEGUI.System.getSingleton() # Create the frame listeners def createFrameListener(self): self.exitListener = ExitListener(self.keyboard) self.root.addFrameListener(self.exitListener) # This is the rendering loop def startRenderLoop(self): self.root.startRendering() # In the end, clean everything up (= delete) def cleanUp(self): self.inputManager.destroyInputObjectKeyboard(self.keyboard) self.inputManager.destroyInputObjectMouse(self.mouse) OIS.InputManager.destroyInputSystem(self.inputManager) self.inputManager = None if __name__ == '__main__': try: ta = Application() ta.go() except ogre.OgreException, e: print e
- Proceed to Basic Tutorial 7 CEGUI and Ogre
| Python-Ogre Tutorials |
|---|
|
Python-Ogre Beginner Tutorials: Beginner 1 - Beginner 2 - Beginner 3 - Beginner 4 - Beginner 5 - Beginner 6 - Beginner 7 - Beginner 8 Intermediate Tutorials: Intermediate 1 - Intermediate 2 - Intermediate 3 - Intermediate 4 - Intermediate 5 - Intermediate 6 Advanced Tutorials: Advanced 1 See also: Artist Tutorials - Ogre Articles - Cookbook |