Basic Tutorial 7
From PyWiki
Beginner Tutorial 7: CEGUI and Ogre
Any problems you encounter while working with this tutorial should be posted to the Help Forum.
Contents |
[edit] 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.
[edit] Introduction
In this tutorial we will be exploring how to use CEGUI (an embedded GUI system) with Ogre. By the end of this tutorial you should be able to add basic CEGUI functionality to your application. NOTE: This tutorial is not intended to fully teach you how to use CEGUI. This tutorial is intended to get you started. All further CEGUI questions and help should be directed to their home page.
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.
[edit] Getting Started
[edit] 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 SampleFramework as sf import ogre.renderer.OGRE as ogre import ogre.io.OIS as OIS import ogre.gui.CEGUI as CEGUI class TutorialListener(sf.FrameListener, OIS.MouseListener, OIS.KeyListener): def __init__(self, renderWindow, camera): sf.FrameListener.__init__(self, renderWindow, camera, True, True) OIS.MouseListener.__init__(self) OIS.KeyListener.__init__(self) self.cont = True self.Mouse.setEventCallback(self) self.Keyboard.setEventCallback(self) def frameStarted(self, evt): self.Keyboard.capture() self.Mouse.capture() return self.cont and not self.Keyboard.isKeyDown(OIS.KC_ESCAPE) def quit(self, evt): self.cont = False return True # MouseListener def mouseMoved(self, evt): return True def mousePressed(self, evt, id): return True def mouseReleased(self, evt, id): return True # KeyListener def keyPressed(self, evt): return True def keyReleased(self, evt): return True class TutorialApplication(sf.Application): def __del__(self): if self.system: del self.system if self.renderer: del self.renderer def _createScene(self): pass def _createFrameListener(self): self.frameListener = TutorialListener(self.renderWindow, self.camera) self.frameListener.showDebugOverlay(True) self.root.addFrameListener(self.frameListener) if __name__ == '__main__': try: ta = TutorialApplication() ta.go() except ogre.OgreException, e: print e
Be sure you can run this code before continuing. The application should do nothing other than present you with a blank screen (press Escape to exit).
[edit] A Brief Introduction
CEGUI is a fully featured GUI library that can be embedded in 3D applications such as Ogre (it also supports pure DirectX and OpenGL as well). Much in the same way that Ogre is only a graphics library (and doesn't do other things such as sound, physics, etc), CEGUI is only a GUI library, meaning it does not do its own rendering nor does it hook into any mouse or keyboard events. In fact, in order for CEGUI to render at all, you have to provide a renderer for it (which is the OgreGUIRenderer library included in the SDK), and in order for it to even understand mouse and keyboard events you have to manually inject them into the system. This may seem like a pain at first, but in reality very little code is required to make this happen. It also allows you to have full control over the rendering and the input, CEGUI will never get in the way.
There are many aspects to CEGUI and many quirks that will be unfamiliar to you (even if you have used GUI systems before). I will try to slowly introduce them to you as we go along.
[edit] Integrating with Ogre
[edit] Initializing CEGUI
We briefly covered how to startup CEGUI in the previous tutorial, so we won't go into any detail for this first portion. Find the createScene function and add the following code:
self.renderer = CEGUI.OgreCEGUIRenderer(self.renderWindow, ogre.RENDER_QUEUE_OVERLAY, False, 3000, self.sceneManager)
self.system = CEGUI.System(self.renderer)
Now that CEGUI has been initialized, we need to choose what skin it will use. CEGUI is highly customizable, and allows you to define the look and feel of your application by changing its skin. We will not be covering how to skin the library in any tutorial, so if you wish to learn more about it, consult the CEGUI website. The following line of code selects the skin:
CEGUI.SchemeManager.getSingleton().loadScheme("TaharezLookSkin.scheme")
The default Ogre install does not come with any other skins for you to use, but if you download CEGUI from their website, you will find several more selections (or you could make your own). The next thing we need to do is set the default mouse cursor image and the default font:
self.system.setDefaultMouseCursor("TaharezLook", "MouseArrow")
self.system.setDefaultFont("BlueHighway-12")
Throughout this tutorial series we will be using CEGUI to display the mouse cursor, even when we have no other use for the GUI library. It is possible to use another GUI library to render the mouse, or to simply create your own mouse cursor using Ogre directly (though this latter option can be a bit involved). If you are only using CEGUI for the mouse cursor and are concerned about memory usage or the disk space that your game takes up, you can look into one of these options to replace CEGUI.
Lastly note that in that last code snippet we have set the default mouse cursor, but we did not set the mouse cursor directly using the MouseCursor.setImage function as we will in later tutorials. This is because in this tutorial we will always be over some kind of CEGUI window (though it may be invisible), so setting the default cursor will, in effect, make the mouse cursor be the image we selected. If we set the mouse cursor directly and did not set the default, the mouse cursor would be invisible every time it passed over a CEGUI window (which, in this tutorial, will be all the time). On the other hand, setting the default mouse image does nothing if you do not have any CEGUI windows displayed, as will be the case in later tutorials. In that situation, calling MouseCursor.setImage will display the cursor for the application.
[edit] Injecting Key Events
CEGUI does not handle input in any way. It does not read mouse movements or keyboard input. Instead it relies on the user to inject key and mouse events into the system. The next thing we will need to do is to handle the key events. If you are working with CEGUI, you will need to have the mouse and keyboard in buffered mode so you can receive the events directly and inject them as they happen. Find the keyPressed function and add the following code to it:
ceguiSystem = CEGUI.System.getSingleton()
ceguiSystem.injectKeyDown(evt.key)
ceguiSystem.injectChar(evt.text)
After getting the system object, we need to do two things. The first is to inject the key down event into CEGUI. The second is to inject the actual character that was pressed. It is very important to inject the character properly since injecting the key down will not always bring about the desired result when using a non-English keyboard. The injectChar was designed with Unicode support in mind.
Now we need to inject the key up event into the system. Find the keyReleased function and add the following code:
CEGUI.System.getSingleton().injectKeyUp(evt.key)
Note that we do not need to inject a character up event, only the key up event is required.
[edit] Converting and Injecting Mouse Events
Now that we have finished dealing with keyboard input, we will now need to take care of mouse input. We have a small issue that we will need to address however. When we injected the key up and down events into CEGUI we never had to convert the key. Both OIS and CEGUI use the same key codes for keyboard input. The same is not true for mouse buttons. Before we can inject mouse button presses into CEGUI, we will need to write a function which converts OIS button IDs into CEGUI button IDs. Add the following function near the top of your source code, just before the TutorialListener class:
def convertButton(oisID):
if oisID == OIS.MB_Left:
return CEGUI.LeftButton
elif oisID == OIS.MB_Right:
return CEGUI.RightButton
elif oisID == OIS.MB_Middle:
return CEGUI.MiddleButton
else:
return CEGUI.LeftButton
Now we are ready to inject mouse events. Find the mousePressed function and add the following code:
CEGUI.System.getSingleton().injectMouseButtonDown(convertButton(id))
This should be roughly self-explanatory. We convert the button ID which was passed in, and pass the result to CEGUI. Find the mouseReleased function and add this line of code:
CEGUI.System.getSingleton().injectMouseButtonUp(convertButton(id))
Lastly, we need to inject mouse motion into CEGUI. The CEGUI.System object has an injectMouseMove function which expects relative mouse movements. The OIS.mouseMoved handler gives us those relative movements in the get_state().X.rel variable and the get_state().Y.rel variables. Find the mouseMoved function and add the following code:
CEGUI.System.getSingleton().injectMouseMove(evt.get_state().X.rel, evt.get_state().Y.rel)
That's it. Now CEGUI is fully setup and receiving mouse and keyboard events.
[edit] Windows, Sheets, and Widgets
[edit] Introduction
CEGUI is very different from most GUI systems. In CEGUI, everything that is displayed is a subclass of the CEGUI.Window class, and a window can have any number of children windows. This means that when you create a frame to contain multiple buttons, that frame is a Window. This also leads to some strange things to happen. You can place a button inside another button, though that would really never happen in practice. The reason that I mention all of this is when you are looking for a particular widget that you have placed in the application, they are all called Windows, and are accessed by functions which call them as such.
In most practical uses of CEGUI, you will not create each individual object through code. Instead you create a GUI layout for your application in an editor such as the CEGUI Layout Editor. After placing all of your windows, buttons, and other widgets onto the screen as you like them, the editor saves the layout into a text file. You may later load this layout into what CEGUI calls a GUI sheet (which is also a subclass of CEGUI.Window).
Lastly, know that CEGUI contains a large number of widgets that you can use in your application. We will not cover them in this tutorial, so if you decide to use CEGUI, be sure to take a good look at their website for more information.
[edit] Loading a Sheet
In CEGUI loading a sheet is very easy to do. The WindowManager class provides a "loadWindowLayout" function which loads the sheet and puts it into a CEGUI.Window object. Then you call CEGUI.System.setGUISheet to display it. We will not be using this in this tutorial, but I would feel remiss if I did not at least show you an example of its use. Do not add this to the tutorial (or if you do, remove it after you have seen the results):
# Do not add this to the program
sheet = CEGUI.WindowManager.getSingleton().loadWindowLayout("ogregui.layout")
self.system.setGUISheet(sheet)
This sets the sheet currently being displayed. You can later retrieve this sheet by calling System.getGUISheet. You can also swap the GUI sheet seamlessly by calling setGUISheet with whatever sheet you want to swap to (though be sure to hold onto a pointer to the current sheet if you wish to swap it back).
[edit] Manually Creating an Object
As I said before, most of the time you use CEGUI, you will be using GUI sheets that you create using an editor. Occasionally, however, you will need to manually create a widget to put on the screen. In this example, we will be adding a Quit button which we will later add functionality to. Since we will be adding more than just the Quit button to the screen by the time the tutorial is over, we need to first create a default CEGUI.Window which will contain all of the widgets we will be creating. Add this to the end of the createScene function:
windowManager = CEGUI.WindowManager.getSingleton()
sheet = windowManager.createWindow("DefaultGUISheet", "CEGUIDemo/Sheet")
This uses the WindowManager to create a "DefaultGUISheet" called "CEGUIDemo/Sheet". While we could name the sheet anything we like, it's very common (and encouraged) to name the widget in a hierarchical manner such as "SomeApp/MainMenu/Submenu3/CancelButton". The next thing we need to do is to create the Quit button and set its size:
quitButton = windowManager.createWindow("TaharezLook/Button", "CEGUIDemo/QuitButton")
quitButton.setText("Quit")
quitButton.setSize(CEGUI.UVector2(CEGUI.UDim(0.15, 0), CEGUI.UDim(0.05, 0)))
This is very close to being cryptic. CEGUI uses a "unified dimension" system for its sizes and positions. When setting the size you must create a UDim object to tell it what size it should be. The first parameter is the relative size of the object in relation to its parent. The second parameter is the absolute size of the object (in pixels). The important thing to realize is that you are only supposed to set one of the two parameters to UDim. The other parameter must be 0. So in this case we have made a button which is 15% as wide as its parent and 5% as tall. If we wanted to specify that it should be 20 pixels by 5 pixels, we would do that by setting the second parameter in both of the UDim calls to be 20 and 5 respectively.
The last thing we have to do is attach the Quit button to the sheet we have created, and then set the current GUI sheet for the system to be that sheet:
sheet.addChildWindow(quitButton)
self.system.setGUISheet(sheet)
Now if you compile and run your application you will see a Quit button in the top left hand corner of the screen, but it does not yet do anything when you click on it.
[edit] Events
Events in CEGUI are very flexible. Instead of using an interface that you implement to receive events, it uses a callback mechanism which binds any public function (with the appropriate method signature) to be the event handler. Unfortunately this also means that registering events is a bit more complicated than it is for Ogre. We will now register to handle the Quit button's click event to exit the program when it is pressed. To do that, we will first need a pointer to the Quit button we created in the previous section. Find the TutorialListener's constructor and add the following code:
windowManager = CEGUI.WindowManager.getSingleton()
quitButton = windowManager.getWindow("CEGUIDemo/QuitButton")
Now that we have a pointer to the button, we now will subscribe to the clicked event. Every widget in CEGUI has a set of events that it supports, and they all begin with "Event". Here is the code to subscribe to the event:
quitButton.subscribeEvent(CEGUI.PushButton.EventClicked, self, "quit")
The first parameter to subscribeEvent is the event itself. The second parameter is the TutorialListener object which will handle the event. The third paramater is the name of the method that will handle the event. That's it! Our TutorialListener.quit function (which has already been defined) will handle the mouse click and terminate the program.
Compile and run your application to test this out.
One thing to note is that we can create any number of functions to handle events for CEGUI. The only restriction on them is that they must return a bool and they must take in a single parameter of type CEGUI.EventArgs. For more information about events (and how to unsubscribe from them), be sure to read more on the CEGUI website.
[edit] Render to Texture
One of the more interesting things we can do with CEGUI is to create a render to texture window. This allows us to create a second Viewport which can be rendered directly into a CEGUI widget. To do this, we first need to setup a scene for us to look at. Add the following code to the bottom of the createScene function:
self.sceneManager.setAmbientLight((1.0, 1.0, 1.0))
self.sceneManager.setSkyDome(True, "Examples/CloudySky", 5, 8)
ogreHead = self.sceneManager.createEntity("Head", "ogrehead.mesh")
headNode = self.sceneManager.getRootSceneNode().createChildSceneNode((0.0, 0.0, -300.0))
headNode.attachObject(ogreHead)
Now we must create the RenderTexture. We use the TextureManager's createManual function with a usage flag of TU_RENDERTARGET to create a texture we can render to, and then fetch the RenderTarget we need to render to the texture. For this program we will create a 512 x 512 texture:
texture = ogre.TextureManager.getSingleton().createManual( "RttTex", "General", ogre.TextureType.TEX_TYPE_2D, 512, 512, 0, ogre.PixelFormat.PF_R8G8B8, ogre.TU_RENDERTARGET ).getBuffer().getRenderTarget()
See the API reference for more information on this function. Next we need to create a Camera and a Viewport to look at the scene we have created. Note that we have changed a couple of Viewport options, including turning off Overlays...which is very important to do or you will get CEGUI and Ogre overlays within our mini-window.
rttCamera = self.sceneManager.createCamera("RttCam")
rttCamera.setPosition(100.0, -100.0, -400.0)
rttCamera.lookAt(0.0, 0.0, -300.0)
viewport = texture.addViewport(rttCamera)
viewport.setOverlaysEnabled(False)
viewport.setClearEveryFrame(True)
viewport.setBackgroundColour(ogre.ColourValue().Black)
Note that we have added the Viewport to the texture itself (as opposed to the RenderWindow, which is where we usually add Viewports). Now that we have created our scene and our texture, we need to embed it within CEGUI. You can create a CEGUI.Texture from any Ogre texture by calling the OgreCEGUIRenderer.createTexture function:
ceguiTexture = self.renderer.createTexture("RttTex")
Unfortunately, this is where things get complicated. In CEGUI you never just deal with a single Texture or a single image. CEGUI works with image sets instead of individual images. It is very useful to work with entire grids of images when you are trying to define the look and feel of a skin you are creating (for example, take a look at TaharezLook.tga in the media folder of the SDK to see what an image set looks like). However, even when you are only trying to define a single image, you must create an entire image set for it. This is what we will be doing:
imageSet = CEGUI.ImagesetManager.getSingleton().createImageset("RttImageset", ceguiTexture)
imageSet.defineImage("RttImage", CEGUI.Rect(0.0, 0.0, ceguiTexture.getWidth(), ceguiTexture.getHeight()), CEGUI.Vector2(0.0, 0.0))
The first line creates the image set (called "RttImageset") from the texture that we have provided it. The next line (which calls defineImage), specifies that the first and only image is called "RttImage" and it is as large as the entire ceguiTexture texture we have provided. Finally we need to create the StaticImage widget which will house the render texture. The first part is no different from creating any other window:
staticImg = windowManager.createWindow("TaharezLook/StaticImage", "RTTWindow")
staticImg.setSize(CEGUI.UVector2(CEGUI.UDim(0.5, 0), CEGUI.UDim(0.4, 0)))
staticImg.setPosition(CEGUI.UVector2(CEGUI.UDim(0.5, 0), CEGUI.UDim(0, 0)))
Now we need to specify which image this StaticImage widget will display. Once again, since CEGUI always deals with image sets and not individual images, we must now retrieve the exact image name from the image set, and to display it:
staticImg.setProperty("Image", CEGUI.PropertyHelper.imageToString(imageSet.getImage("RttImage")))
If it seems like we have packed a texture into an image set only to unpack it again, it's because that's exactly what we have done. Manipulating images in CEGUI is not one of the easiest or most straightforward things in the library. The last thing we need to do is to add the StaticImage widget to the GUI sheet we created earlier:
sheet.addChildWindow(staticImg)
Now we are finished. Compile and run the application.
[edit] Conclusion
[edit] Alternatives
CEGUI is not without its quirks and flaws, and it's definitely not for everyone. Here are a few alternatives to CEGUI:
[edit] More Information
There are also several other places you can get more information about CEGUI.
- Practical Application - Something With A Bit More Meat - A more in-depth tutorial than the one here.
- CEGUI's Official Tutorials
- The CEGUI Website
- Proceed to Basic Tutorial 8 Using Multiple SceneManagers
| 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 |
